// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包PAR实现并行执行帮助器。
package par

import (
	"math/rand"
	"sync"
	"sync/atomic"
)

// Work管理一组并行执行的工作项，每个项最多执行一次。
// 集合中的项目必须都是有效的映射键。
type Work struct {
	f       func(interface{}) // 要为每个项目运行的函数
	running int               // 跑步者总数

	mu      sync.Mutex
	added   map[interface{}]bool // 添加到集合中的项目
	todo    []interface{}        // 尚未运行的项目
	wait    sync.Cond            // 等待todo为空
	waiting int                  // 等待todo的跑步者人数
}

func (w *Work) init() {
	if w.added == nil {
		w.added = make(map[interface{}]bool)
	}
}

// 添加将项添加到工作集（如果尚未添加）。
func (w *Work) Add(item interface{}) {
	w.mu.Lock()
	w.init()
	if !w.added[item] {
		w.added[item] = true
		w.todo = append(w.todo, item)
		if w.waiting > 0 {
			w.wait.Signal()
		}
	}
	w.mu.Unlock()
}

// 在工作集中的项目上并行运行f，
// 一次最多运行n次f调用。
// 当添加到工作集的所有内容都已处理时，它将返回。
// 至少应将一项添加到工作集中
// 在调用Do（或Do立即返回）之前，
// 但允许f（项）向集合中添加新项。
// Do只能在给定的工作中使用一次。
func (w *Work) Do(n int, f func(item interface{})) {
	if n < 1 {
		panic("par.Work.Do: n < 1")
	}
	if w.running >= 1 {
		panic("par.Work.Do: already called Do")
	}

	w.running = n
	w.f = f
	w.wait.L = &w.mu

	for i := 0; i < n-1; i++ {
		go w.runner()
	}
	w.runner()
}

// runner在w中执行工作，直到两个都不需要做任何事情
// 所有的跑步者都在等待工作。
// （然后所有跑步者都返回。）
func (w *Work) runner() {
	for {
		// 等待做某事。
		w.mu.Lock()
		for len(w.todo) == 0 {
			w.waiting++
			if w.waiting == w.running {
				// 都做完了。
				w.wait.Broadcast()
				w.mu.Unlock()
				return
			}
			w.wait.Wait()
			w.waiting--
		}

		// 随便挑一件事做，
		// 消除病态争论
		// 如果项目几乎同时添加
		// 他们最有可能争辩。
		i := rand.Intn(len(w.todo))
		item := w.todo[i]
		w.todo[i] = w.todo[len(w.todo)-1]
		w.todo = w.todo[:len(w.todo)-1]
		w.mu.Unlock()

		w.f(item)
	}
}

// 缓存对每个键运行一次操作并缓存结果。
type Cache struct {
	m sync.Map
}

type cacheEntry struct {
	done   uint32
	mu     sync.Mutex
	result interface{}
}

// 当且仅当Do首次使用此键被调用时，Do调用函数f。
// 在对f的一个调用返回之前，对给定键的Do调用不会返回。
// Do返回对f的一次调用返回的值。
func (c *Cache) Do(key interface{}, f func() interface{}) interface{} {
	entryIface, ok := c.m.Load(key)
	if !ok {
		entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
	}
	e := entryIface.(*cacheEntry)
	if atomic.LoadUint32(&e.done) == 0 {
		e.mu.Lock()
		if atomic.LoadUint32(&e.done) == 0 {
			e.result = f()
			atomic.StoreUint32(&e.done, 1)
		}
		e.mu.Unlock()
	}
	return e.result
}

// Get返回与键关联的缓存结果。
// 如果没有这样的结果，则返回nil。
// 如果正在计算key的结果，Get不会等待计算完成。
func (c *Cache) Get(key interface{}) interface{} {
	entryIface, ok := c.m.Load(key)
	if !ok {
		return nil
	}
	e := entryIface.(*cacheEntry)
	if atomic.LoadUint32(&e.done) == 0 {
		return nil
	}
	return e.result
}

// 清除将删除缓存中的所有条目。
// None
// 对Get的并发调用可能返回旧值。要做的并发调用
// 可以返回旧值或将结果存储在已删除的条目中。
// None
// TODO（jayconrod）：在包缓存清除功能完成后删除此项
// 已移除内部/负载。
func (c *Cache) Clear() {
	c.m.Range(func(key, value interface{}) bool {
		c.m.Delete(key)
		return true
	})
}

// 删除从映射中删除条目。可以安全地调用Delete以获取
// 不存在的条目。删除将快速返回，即使结果
// 因为密钥仍在计算中；计算将完成，但
// 无法通过缓存访问结果。
// None
// TODO（jayconrod）：在包缓存清除功能完成后删除此项
// 已移除内部/负载。
func (c *Cache) Delete(key interface{}) {
	c.m.Delete(key)
}

// DeleteIf为映射中的每个键调用pred。如果pred为键返回true，
// DeleteIf删除相应的条目。如果密钥的结果为
// DeleteIf仍在计算中，它将删除该条目而不等待
// 要完成的计算。无法通过缓存访问结果。
// None
// TODO（jayconrod）：在包缓存清除功能完成后删除此项
// 已移除内部/负载。
func (c *Cache) DeleteIf(pred func(key interface{}) bool) {
	c.m.Range(func(key, _ interface{}) bool {
		if pred(key) {
			c.Delete(key)
		}
		return true
	})
}
